package org.arl.modem.transportARQ;

import org.arl.modem.phy.SchemeParam;

/**
 * Class to perform all calculations based on type of transmission
 * @author rohit
 * 
 */
public class Calculator {
   private double alpha_d = 0;
   private double alpha_c = 0;
   private double beta_d = 0;
   private double beta_c = 0;
   private double delta_min = .080;// (seconds)
   private double ec;
   private double ed;
   private double gamma_c;
   private double gamma_d;
   private double p = TransportARQ.propDelay;
   private double theta;
   // for JARQ
   public static double best_delta;
   private int best_k = 0;
   private int best_m = 0;
   private int best_nd = 0;
   private TransportARQ tarq;
   private int nd_arq;
   private int m_arq;

   public Calculator(double alpha_d, double alpha_c, double beta_d,
         double beta_c, TransportARQ tarq) {
      super();
      this.alpha_d = alpha_d;
      this.alpha_c = alpha_c;
      this.beta_d = beta_d;
      this.beta_c = beta_c;
      this.ec = TransportARQ.BER_CONTROL; // BER of control pkt
      this.ed = TransportARQ.BER_DATA; // BER of data pkt
      this.gamma_c = TransportARQ.DETECTION_PROB_CONTROL;
      this.gamma_d = TransportARQ.DETECTION_PROB_DATA;
      this.theta = (double) (TransportARQ.FIXED_PROC_TIME
            + TransportARQ.VAR_PROC_TIME + TransportARQ.GUARD_TIME) / 1000.0;

      this.tarq = tarq;
      tarq.mylog.fine("theta:" + theta);
      // this.theta = .285;
      // this.alpha_d = this.alpha_d + this.theta / 2.0;
      // this.alpha_c = this.alpha_c + this.theta / 2.0;
      this.alpha_d = this.alpha_d + .07;
      this.alpha_c = this.alpha_c + .07;

      tarq.mylog.fine("NEW tsp:alpha_c:beta_c:" + this.alpha_c + ":"
            + this.beta_c);
      tarq.mylog.fine("NEW tsp:alpha_d:beta_d:" + this.alpha_d + ":"
            + this.beta_d);

      initARQ();
      initJARQ();
   }

   private void initARQ() {
      // TODO Auto-generated method stub
      int nd = 0;
      double lamda = alpha_d + beta_c;
      double temp = Math.sqrt(lamda
            * (lamda - 4.0 * beta_d / (Math.log(1 - ed))))
            - lamda;
      temp = temp / (2.0 * beta_d);

      nd = (int) Math.round(temp);

      nd = modify_nd_ifneeded(nd);
      nd_arq = nd;
      int m = 0;
      double x = 2.0 * p + alpha_c;
      // double lamda = alpha_d + beta_c;
      double temp1 = lamda + get_nd() * beta_d;
      double temp2 = Math.sqrt(4.0 * x * temp1 * ec) - x * ec;
      temp2 = temp2 / (2.0 * temp1 * ec);
      m = (int) Math.round(temp2);
      m_arq = m;

   }

   // at~pp.3.21?,22?,23?,24?,256?,257?,258?,259?,260?,261?,262?
   public int get_nd() {
      return nd_arq;
   }

   /**
    * 
    * @return
    */
   public int get_m() {

      return m_arq;
   }

   private void initJARQ() {
      double nd_shift = (alpha_d + beta_c) / beta_d;
      double nd_min = 1;
      int nd_opt = (int) Math.round(-1.0 / Math.log(1 - ed));
      System.out.println("JARQ: nd optimal=" + nd_opt);
      nd_opt = modify_nd_ifneeded(nd_opt);
      int k_max = (int) Math.floor(2.0 * p
            / (alpha_d + beta_c + nd_min * beta_d + alpha_c + delta_min));
      double max_phi = 0;
      // %% search over k
      for (int k = 1; k <= k_max; k++) {
         // %% set search range for m
         int m_max = (int) Math.floor((2.0 * p / k - alpha_c - delta_min)
               / (alpha_d + beta_c + nd_min * beta_d));
         double c = (2.0 * p / k - alpha_c - delta_min) / beta_d;
         // %% find nd values
         for (int m = 1; m <= m_max; m++) {

            int nd_val = (int) Math.floor(c / m - nd_shift);
            if (nd_val > nd_opt) {
               nd_val = (int) nd_opt;
            }
            if (nd_val < 1) {
               nd_val = 1;
            }
            double delta = 2.0 * p / k - alpha_c - m
                  * (alpha_d + beta_c + beta_d * nd_val);
            double t2 = 2.0 * p / k;
            double n2 = (Math.pow((1 - ec), m)) * (Math.pow((1 - ed), nd_val))
                  * m * nd_val * (gamma_c * gamma_d);
            double phi_2 = n2 / t2; // %% No need to use boolean as the
                                    // constraint is met.
            if (phi_2 >= max_phi) {
               max_phi = phi_2;
               best_k = k;
               best_delta = delta;
               best_m = m;
               best_nd = nd_val;
            }
         }
      }
      tarq.mylog.fine("Jarq values k, m, nd, delta:" + get_k_jarq() + ":"
            + get_m_jarq() + ":" + get_nd_jarq() + " : " + +getBest_delta());
      tarq.mylog.fine("ARQ(m,nd):" + get_m() + ":" + get_nd());
      System.out.println("Jarq values k, m, nd, delta:" + get_k_jarq() + ":"
            + get_m_jarq() + ":" + get_nd_jarq() + " : " + +getBest_delta());
      System.out.println("ARQ(m,nd):" + get_m() + ":" + get_nd());
   }

   public int get_m_jarq() {
      return best_m;
   }

   public int get_nd_jarq() {
      return best_nd;
   }

   public int get_k_jarq() {
      return best_k;
   }

   // public static void main(String args[]) {
   // // Calculator calc = new Calculator(.03, .03, 1.0 / 2500.0, 1.0 / 250.0);
   // Calculator calc = new Calculator(.418, .418, 0.0035416666666666665,
   // 0.0035416666666666665);
   //
   // // .042625 .418
   // tarq.mylog.fine("values k, m, nd, delta:" + calc.get_k_jarq() + ":"
   // + calc.get_m_jarq() + ":" + calc.get_nd_jarq() + "  : ");
   // tarq.mylog.fine("ARQ(m,nd):" + calc.get_m() + ":" + calc.get_nd());
   //
   // }
   private int modify_nd_ifneeded(int nd) {
      // System.out.println("nd before modification:" + nd);
      int modnd = nd;
      int len = (int) (Math.round(modnd / 8.0)
            + TransportARQ.otherOverheadBytes + TransportARQ.myOverheadBytes);// 7;
      if (len % 3 != 0)
         len = len + (3 - len % 3);
      tarq.setPhyParam(this.tarq.SCHEME_DATA, SchemeParam.PKTLEN, len);
      int pktdur = tarq.getPhyParam(this.tarq.SCHEME_DATA,
            SchemeParam.PKT_DURATION);
      int pktdur2 = pktdur;
      // System.out.println("packet duration=" + pktdur2);
      if (pktdur2 < TransportARQ.MAXPKTDURATION)
         return nd;
      while (pktdur2 > TransportARQ.MAXPKTDURATION) {
         len = len - 3;
         pktdur2 = (int) (pktdur2 - Math.round(3 * this.beta_d * 8 * 1000));
      }
      tarq.setPhyParam(this.tarq.SCHEME_DATA, SchemeParam.PKTLEN, len);
      modnd = (len - (TransportARQ.otherOverheadBytes + TransportARQ.myOverheadBytes)) * 8;
      // System.out.println("nd after modification:" + modnd);
      return modnd;
   }

   public double getBest_delta() {
      return best_delta;
   }

}
